/*
    TwoPageView image viewer
    Copyright (C) 2006 Japan Manoretimedia Lab.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

using System;
using System.Collections.Generic;
using System.Text;

namespace TwoPageView
{
    public class BackgroundDirectoryIterator
    {
        private System.ComponentModel.BackgroundWorker bwControl;
        private int bwControlThreadId = -1;

        private BackgroundWorkerWithProcess[] bws;
        private System.Threading.AutoResetEvent[] loadCompleteEvents;

        public int ChildProcessNum
        {
            get { return bws.Length; }
        }
        public BackgroundWorkerWithProcess.Status ChildProcessWorkStatusGet(int nthChild)
        {
            if (nthChild < 0 || ChildProcessNum <= nthChild) {
                return null;
            }
            return bws[nthChild].StatusGet();
        }

        public class DoWorkOnOneFileArg
        {
            public int workId;
            public bool isDragDropDir;
            public System.IO.FileInfo fi;
            public string dFullName;
        }

        /// <summary>
        /// do paralleled directory iteration 
        /// </summary>
        /// <param name="nWorkerThread">number of files processing concurrently DoWorkOnOneFileArg</param>
        /// <param name="DoWorkOnOneFile">called every file on specified folder with DoWorkOnOneFileArg argument.</param>
        public BackgroundDirectoryIterator(int nWorkerThread, System.ComponentModel.DoWorkEventHandler DoWorkOnOneFile, 
            System.ComponentModel.ProgressChangedEventHandler IterateProgressChanged,
            System.ComponentModel.RunWorkerCompletedEventHandler IterateCompleted)
        {
            bws = new BackgroundWorkerWithProcess[nWorkerThread];
            loadCompleteEvents = new System.Threading.AutoResetEvent[nWorkerThread];

            for (int i = 0; i < nWorkerThread; ++i) {
                BackgroundWorkerWithProcess bw = new BackgroundWorkerWithProcess();
                bw.Index = i;
                bw.ChildProcessStart();
                bw.DoWork += DoWorkOnOneFile;
                bw.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(Bw_RunWorkerCompleted);
                bws[i] = bw;
                loadCompleteEvents[i] = new System.Threading.AutoResetEvent(false);
            }

            bwControl = new System.ComponentModel.BackgroundWorker();
            bwControl.WorkerSupportsCancellation = true;
            bwControl.WorkerReportsProgress = true;
            bwControl.DoWork += new System.ComponentModel.DoWorkEventHandler(BwControl_DoWork);
            bwControl.ProgressChanged += IterateProgressChanged;
            bwControl.RunWorkerCompleted += IterateCompleted;
        }
        
        public void RunWorkerAsync(DirectoryOrZipDirInfo di)
        {
            bwControl.RunWorkerAsync(di);
        }

        /// <summary>
        /// cancel Folder iteration. if backgroundthread is not working, do nothing
        /// Called from the UI thread
        /// </summary>
        /// <returns>true: backgroundworker is running(canceling), false: backgroundworker is not running</returns>
        public bool CancelFromUIThread(bool bBlock)
        {
            Console.WriteLine("D: BackgroundDirectoryIterator.CancelFromUIThread(bBlock={0})", bBlock);
            if (!bwControl.IsBusy) {
                return false;
            }
            bwControl.CancelAsync();
            if (bBlock) {
                while (bwControl.IsBusy) {
                    System.Windows.Forms.Application.DoEvents();
                    System.Threading.Thread.Sleep(100);
                }
            }
            return bwControl.IsBusy;
        }

        public MyTriBool QueryCancel()
        {
            if (System.Threading.Thread.CurrentThread.ManagedThreadId == bwControlThreadId) {
                return bwControl.CancellationPending ? MyTriBool.True : MyTriBool.False;
            }
            return MyTriBool.Indeterminate;
        }

        private void BwControl_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
        {
            System.Threading.Thread.CurrentThread.Priority = System.Threading.ThreadPriority.BelowNormal;

            System.ComponentModel.BackgroundWorker bw = (System.ComponentModel.BackgroundWorker)sender;
            DirectoryOrZipDirInfo d = (DirectoryOrZipDirInfo)e.Argument;

            Console.WriteLine("D: BwControl_DoWork() Start {0}", d.FullName);
            bwControlThreadId = System.Threading.Thread.CurrentThread.ManagedThreadId;
            e.Result = true;
            try {
                IterateFiles(ref bw, d);
            } catch (UnauthorizedAccessException) {
            } catch (Exception ex) {
                Console.WriteLine(ex);
            }
            if (bw.CancellationPending) {
                Console.WriteLine("D: BwControl_DoWork() Cancelled");
                e.Cancel = true;
            }
            bwControlThreadId = -1;
            Console.WriteLine("D: BwControl_DoWork() End");
        }

        private void IterateFiles(ref System.ComponentModel.BackgroundWorker bw, DirectoryOrZipDirInfo d)
        {
            Console.WriteLine("D: IterateFiles({0}) start", d.FullName);
            bw.ReportProgress(0);
            System.IO.FileInfo[] fis = null;
            fis = d.GetFiles();
            if (null == fis) {
                return;
            }
            System.Collections.Generic.SortedList<string, System.IO.FileInfo> mapForSort =
                new System.Collections.Generic.SortedList<string, System.IO.FileInfo>();
            foreach (System.IO.FileInfo fi in fis) {
                mapForSort.Add(fi.Name, fi);
            }

            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
            sw.Start();
            int nProgress = 0;
            foreach (System.Collections.Generic.KeyValuePair<string, System.IO.FileInfo> sfp in mapForSort) {
                DoWorkOnOneFileArg arg = new DoWorkOnOneFileArg();
                arg.workId = nProgress;
                arg.fi = sfp.Value;
                arg.isDragDropDir = d.IsDragDropDir;
                arg.dFullName = d.FullName;

                // first nWorker files on the folder are processed immediately.
                // following files will be processed after the first works are completed.
                if (nProgress < bws.Length) {
                    bws[nProgress].RunWorkerAsync(arg);
                } else {
                    int idleWorkerIndex = System.Threading.WaitHandle.WaitAny(loadCompleteEvents);
                    bws[idleWorkerIndex].RunWorkerAsync(arg);
                }
                ++nProgress;
                int progressPercentage = 100 * nProgress / fis.Length;
                if (1 == nProgress || 500 < sw.ElapsedMilliseconds) {
                    //Console.WriteLine("D: ReportProgress");
                    bw.ReportProgress(progressPercentage);
                    sw.Reset();
                    sw.Start();
                }
                if (bw.CancellationPending) {
                    Console.WriteLine("D: IterateFiles() Cancel");
                    break;
                }
            }
            sw.Stop();
            // Console.WriteLine("D: bwControl_DoWork WaitAll start");
            if (nProgress < loadCompleteEvents.Length) {
                for (int i = nProgress; i < loadCompleteEvents.Length; ++i) {
                    loadCompleteEvents[i].Set();
                }
            }
            System.Threading.WaitHandle.WaitAll(loadCompleteEvents);
            bw.ReportProgress(100);
            Console.WriteLine("D: IterateFiles() End");
        }

        private void Bw_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
        {
            BackgroundWorkerWithProcess bw = sender as BackgroundWorkerWithProcess;
            loadCompleteEvents[bw.Index].Set();
        }
    }
}
